/*
 * Copyright (c) 2020-2021, SERI Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-06-23     Lyons        first version
 */

uint32_t exec_lui(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->imm << 12 );
}

uint32_t exec_auipc(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->current_pc + (inst_info->imm << 12) );
}

uint32_t exec_jal(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->next_pc;
    inst_info->imm <<= 1; // imm20 << 1
    inst_info->next_pc = (int32_t)inst_info->current_pc + (int32_t)SIGN_EXTEND(inst_info->imm, 21);
    return iresult;
}

uint32_t exec_jalr(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->next_pc;
    inst_info->next_pc  = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->next_pc &= 0xfffffffe;
    return iresult;
}

uint32_t exec_beq(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->imm <<= 1; // im12 << 1
    if ( inst_info->rs1_data == inst_info->rs2_data )
        inst_info->next_pc = (int32_t)inst_info->current_pc + (int32_t)SIGN_EXTEND(inst_info->imm, 13);
    return 0;
}

uint32_t exec_bne(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->imm <<= 1; // im12 << 1
    if ( inst_info->rs1_data != inst_info->rs2_data )
        inst_info->next_pc = (int32_t)inst_info->current_pc + (int32_t)SIGN_EXTEND(inst_info->imm, 13);
    return 0;
}

uint32_t exec_blt(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->imm <<= 1; // im12 << 1
    if ( (int32_t)inst_info->rs1_data < (int32_t)inst_info->rs2_data )
        inst_info->next_pc = (int32_t)inst_info->current_pc + (int32_t)SIGN_EXTEND(inst_info->imm, 13);
    return 0;
}

uint32_t exec_bge(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->imm <<= 1; // im12 << 1
    if ( (int32_t)inst_info->rs1_data >= (int32_t)inst_info->rs2_data )
        inst_info->next_pc = (int32_t)inst_info->current_pc + (int32_t)SIGN_EXTEND(inst_info->imm, 13);
    return 0;
}

uint32_t exec_bltu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->imm <<= 1; // im12 << 1
    if ( inst_info->rs1_data < inst_info->rs2_data )
        inst_info->next_pc = (int32_t)inst_info->current_pc + SIGN_EXTEND(inst_info->imm, 13);
    return 0;
}

uint32_t exec_bgeu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->imm <<= 1; // im12 << 1
    if ( inst_info->rs1_data >= inst_info->rs2_data )
        inst_info->next_pc = (int32_t)inst_info->current_pc + SIGN_EXTEND(inst_info->imm, 13);
    return 0;
}

uint32_t exec_lb(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mem_addr = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->mem_rd = 1;
    inst_info->mem_ext = eMemoryExtType_Sign;
    inst_info->mem_size = eMemorySize_Byte;
    return 0;
}

uint32_t exec_lh(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mem_addr = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->mem_rd = 1;
    inst_info->mem_ext = eMemoryExtType_Sign;
    inst_info->mem_size = eMemorySize_HalfWord;
    return 0;
}

uint32_t exec_lw(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mem_addr = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->mem_rd = 1;
    inst_info->mem_ext = eMemoryExtType_Sign;
    inst_info->mem_size = eMemorySize_Word;
    return 0;
}

uint32_t exec_lbu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mem_addr = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->mem_rd = 1;
    inst_info->mem_ext = eMemoryExtType_Zero;
    inst_info->mem_size = eMemorySize_Byte;
    return 0;
}

uint32_t exec_lhu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mem_addr = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->mem_rd = 1;
    inst_info->mem_ext = eMemoryExtType_Zero;
    inst_info->mem_size = eMemorySize_HalfWord;
    return 0;
}

uint32_t exec_sb(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mem_addr = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->mem_data = inst_info->rs2_data;
    inst_info->mem_we = 1;
    inst_info->mem_size = eMemorySize_Byte;
    return 0;
}

uint32_t exec_sh(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mem_addr = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->mem_data = inst_info->rs2_data;
    inst_info->mem_we = 1;
    inst_info->mem_size = eMemorySize_HalfWord;
    return 0;
}

uint32_t exec_sw(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mem_addr = (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12);
    inst_info->mem_data = inst_info->rs2_data;
    inst_info->mem_we = 1;
    inst_info->mem_size = eMemorySize_Word;
    return 0;
}

uint32_t exec_addi(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( (int32_t)inst_info->rs1_data + (int32_t)SIGN_EXTEND(inst_info->imm, 12) );
}

uint32_t exec_slli(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data << inst_info->shamt );
}

uint32_t exec_slti(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( ((int32_t)inst_info->rs1_data < (int32_t)SIGN_EXTEND(inst_info->imm, 12)) ? 1 : 0 );
}

uint32_t exec_sltiu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( (inst_info->rs1_data < SIGN_EXTEND(inst_info->imm, 12)) ? 1 : 0 );
}

uint32_t exec_xori(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data ^ SIGN_EXTEND(inst_info->imm, 12) );
}

uint32_t exec_srli(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data >> inst_info->shamt );
}

uint32_t exec_srai(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->rs1_data >> inst_info->shamt;
    uint32_t mask = SIGN_EXTEND(BIT(inst_info->rs1_data, 31), 1) >> inst_info->shamt;
    if ( BIT(inst_info->rs1_data, 31) ) {
        iresult |= ~mask;
    }
    return iresult;
}

uint32_t exec_ori(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data | SIGN_EXTEND(inst_info->imm, 12) );
}

uint32_t exec_andi(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data & SIGN_EXTEND(inst_info->imm, 12) );
}

uint32_t exec_add(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( (int32_t)inst_info->rs1_data + (int32_t)inst_info->rs2_data );
}

uint32_t exec_sub(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( (int32_t)inst_info->rs1_data - (int32_t)inst_info->rs2_data );
}

uint32_t exec_sll(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data << inst_info->rs2_data );
}

uint32_t exec_slt(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( ((int32_t)inst_info->rs1_data < (int32_t)inst_info->rs2_data) ? 1 : 0 );
}

uint32_t exec_sltu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( (inst_info->rs1_data < inst_info->rs2_data) ? 1 : 0 );
}

uint32_t exec_xor(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data ^ inst_info->rs2_data );
}

uint32_t exec_srl(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data >> inst_info->rs2_data );
}

uint32_t exec_sra(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->rs1_data >> inst_info->rs2_data;
    uint32_t mask = SIGN_EXTEND(BIT(inst_info->rs1_data, 31), 1) >> inst_info->rs2_data;
    if ( BIT(inst_info->rs1_data, 31) ) {
        iresult |= ~mask;
    }
    return iresult;
}

uint32_t exec_or(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data | inst_info->rs2_data );
}

uint32_t exec_and(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return ( inst_info->rs1_data & inst_info->rs2_data );
}

uint32_t exec_fence(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    return 0;
}

uint32_t exec_mret(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    inst_info->mret = 1;
    return 0;
}

uint32_t exec_csrrw(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->csr_data;
    inst_info->csr_data = inst_info->rs1_data;
    return iresult;
}

uint32_t exec_csrrs(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->csr_data;
    inst_info->csr_data |= inst_info->rs1_data;
    return iresult;
}

uint32_t exec_csrrc(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->csr_data;
    inst_info->csr_data &= ~inst_info->rs1_data;
    return iresult;
}

uint32_t exec_csrrwi(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->csr_data;
    inst_info->csr_data = ZERO_EXTEND(inst_info->imm, 5);
    return iresult;
}

uint32_t exec_csrrsi(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->csr_data;
    inst_info->csr_data |= ZERO_EXTEND(inst_info->imm, 5);
    return iresult;
}

uint32_t exec_csrrci(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->csr_data;
    inst_info->csr_data &= ~ZERO_EXTEND(inst_info->imm, 5);
    return iresult;
}


#ifdef RV32M

uint32_t exec_mul(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint64_t data1 = (uint64_t)inst_info->rs1_data + (BIT(inst_info->rs1_data, 31) ? 0xffffffff00000000 : 0);
    uint64_t data2 = (uint64_t)inst_info->rs2_data + (BIT(inst_info->rs2_data, 31) ? 0xffffffff00000000 : 0);
    uint64_t result64 = (int64_t)data1 * (int64_t)data2;
    return ( result64 );
}

#ifndef __linux__
#pragma GCC diagnostic ignored "-Wshift-count-overflow"
#endif

uint32_t exec_mulh(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint64_t data1 = (uint64_t)inst_info->rs1_data + (BIT(inst_info->rs1_data, 31) ? 0xffffffff00000000 : 0);
    uint64_t data2 = (uint64_t)inst_info->rs2_data + (BIT(inst_info->rs2_data, 31) ? 0xffffffff00000000 : 0);
    uint64_t result64 = (int64_t)data1 * (int64_t)data2;
    return ( result64 >> 32 );
}

uint32_t exec_mulhsu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint64_t data1 = (uint64_t)inst_info->rs1_data + (BIT(inst_info->rs1_data, 31) ? 0xffffffff00000000 : 0);
    uint64_t data2 = (uint64_t)inst_info->rs2_data;
    uint64_t result64 = (int64_t)data1 * (int64_t)data2;
    return ( result64 >> 32 );
}

uint32_t exec_mulhu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint64_t data1 = (uint64_t)inst_info->rs1_data;
    uint64_t data2 = (uint64_t)inst_info->rs2_data;
    uint64_t result64 = (int64_t)data1 * (int64_t)data2;
    return ( result64 >> 32 );
}

uint32_t exec_div(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = 0xffffffff;
    if ( 0 != (int32_t)inst_info->rs2_data ) {
        iresult = (int32_t)inst_info->rs1_data / (int32_t)inst_info->rs2_data;
    }
    return iresult;
}

uint32_t exec_divu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = 0xffffffff;
    if ( 0 != (uint32_t)inst_info->rs2_data ) {
        iresult = (uint32_t)inst_info->rs1_data / (uint32_t)inst_info->rs2_data;
    }
    return iresult;
}
        
uint32_t exec_rem(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->rs1_data;
    if ( 0 != (int32_t)inst_info->rs2_data ) {
        iresult = (int32_t)inst_info->rs1_data % (int32_t)inst_info->rs2_data;
    }
    return iresult;
}

uint32_t exec_remu(void *info)
{
    InstInfo_t *inst_info = (InstInfo_t*)info;
    uint32_t iresult = inst_info->rs1_data;
    if ( 0 != (uint32_t)inst_info->rs2_data ) {
        iresult = (uint32_t)inst_info->rs1_data % (uint32_t)inst_info->rs2_data;
    }
    return iresult;
}

#endif // #ifdef RV32M
